package com.yj.spring.charpter04;

import cn.hutool.core.util.StrUtil;
import com.yj.spring.common.ArgumentValue;
import com.yj.spring.common.PropertyValue;
import lombok.SneakyThrows;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class SimpleBeanFactory extends DefaultBeanDefinitionRegistry implements BeanFactory {

    List<String> beanNames = new ArrayList<>();
    Map<String, Object> singletonObjects = new HashMap<>();

    Map<String, Object> earlySingletonObjects = new HashMap<>();

    @Override
    public Object getBean(String beanName) {

        // 先尝试从一级缓存中获取
        Object bean = singletonObjects.get(beanName);

        if (bean == null) {
            // 再尝试从二级缓存中获取
            bean = this.earlySingletonObjects.get(beanName);

            if (bean == null){
                bean = createBean(beanDefinitionMap.get(beanName));
                this.registerBean(beanName, bean);
            }
        }


        return bean;
    }

    private Object createBean(BeanDefinition beanDefinition) {
        String beanName = beanDefinition.getId();
        try {

            Object bean = doCreateBean(beanDefinition);

            this.earlySingletonObjects.put(beanName, bean);

            // 属性赋值
            populateBean(beanDefinition, bean);
            // 预留beanpostprocessor位置
            // step 1: postProcessBeforeInitialization
            // step 2: afterPropertiesSet
            // step 3: init-method
            // step 4: postProcessAfterInitialization
            //initializeBean(beanName, bean);

            return bean;
        }catch (Exception e){
            throw new RuntimeException(beanName, e);
        }

    }

    @SneakyThrows
    private void populateBean(BeanDefinition beanDefinition, Object bean) {
        List<PropertyValue> propertyValues = beanDefinition.getPropertyValues();
        Class<?> aClass = bean.getClass();
        Class[] propertyType = new Class[1];
        Object[] propertyParameter = new Object[1];
        for (int i = 0; i < propertyValues.size(); i++) {
            PropertyValue propertyValue = propertyValues.get(i);
            String type = propertyValue.getType();
            String ref = propertyValues.get(i).getRef();
            if (StrUtil.isNotEmpty(ref)){
                propertyType[0] = Class.forName(type);
                Object refBean = this.getBean(ref);
                propertyParameter[0] = refBean;
            }else{

                if ("int".equals(type) || "java.lang.Integer".equals(type)) {
                    propertyType[0] = Integer.class;
                }else if ("string".equals(type) || "java.lang.String".equals(type)){
                    propertyType[0] = String.class;
                }else if ("boolean".equals(type) || "java.lang.Boolean".equals(type)){
                    propertyType[0] = Boolean.class;
                }else {
                    propertyType[0] = String.class;
                }
                propertyParameter[0] = propertyValue.getValue();

            }
            String methodName = "set" + propertyValue.getName().substring(0, 1).toUpperCase() + propertyValue.getName().substring(1);
            Method method = aClass.getMethod(methodName, propertyType);
            method.invoke(bean, propertyParameter);

        }

    }

    @SneakyThrows
    private Object doCreateBean(BeanDefinition beanDefinition) {

        Object bean = null;


        List<ArgumentValue> argumentValues = beanDefinition.getArgumentValues();
        Class<?> aClass = Class.forName(beanDefinition.getClassName());
        Object[] parameters= new Object[argumentValues.size()];
        Class[] parameterTypes = new Class[argumentValues.size()];

        for (int i = 0; i < argumentValues.size(); i++) {
            ArgumentValue argumentValue = argumentValues.get(i);
            String type = argumentValue.getType();
            if ("int".equals(type) || "java.lang.Integer".equals(type)){
                parameters[i] = Integer.parseInt((String) argumentValue.getValue());
                parameterTypes[i] = int.class;
            }else if ("String".equals(type) || "java.lang.String".equals(type)){
                parameters[i] = argumentValue.getValue();
                parameterTypes[i] = String.class;
            }else if ("boolean".equals(type) || "java.lang.Boolean".equals(type)) {
                parameters[i] = Boolean.parseBoolean((String) argumentValue.getValue());
                parameterTypes[i] = boolean.class;
            }else{
                parameters[i] = argumentValue.getValue();
                parameterTypes[i] = String.class;
            }
        }
        bean = aClass.getDeclaredConstructor(parameterTypes).newInstance(parameters);
        return bean;
    }

    @Override
    public void registerBean(String beanName, Object bean) {
        beanNames.add(beanName);
        singletonObjects.put(beanName, bean);
    }

    @Override
    public void removeBean(String beanName) {
        beanNames.remove(beanName);
        singletonObjects.remove(beanName);
    }

    //public abstract Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName);
    //
    //public abstract Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName);
    //
    //public abstract void initializeBean(String beanName, Object bean);
}
